package com.easychat.service.impl;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import com.easychat.constants.Constants;
import com.easychat.entity.config.AppConfig;
import com.easychat.entity.dto.MessageSendDTO;
import com.easychat.entity.dto.SysSettingDTO;
import com.easychat.entity.dto.TokenUserInfoDTO;
import com.easychat.entity.enums.*;
import com.easychat.entity.po.ChatSession;
import com.easychat.entity.po.UserContact;
import com.easychat.entity.query.ChatSessionQuery;
import com.easychat.entity.query.UserContactQuery;
import com.easychat.exception.BusinessException;
import com.easychat.mappers.ChatSessionMapper;
import com.easychat.mappers.UserContactMapper;
import com.easychat.redis.RedisComponent;
import com.easychat.service.AsyncService;
import com.easychat.utils.DateUtil;
import com.easychat.websocket.MessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.easychat.entity.query.ChatMessageQuery;
import com.easychat.entity.po.ChatMessage;
import com.easychat.entity.vo.PaginationResultVO;
import com.easychat.entity.query.SimplePage;
import com.easychat.mappers.ChatMessageMapper;
import com.easychat.service.ChatMessageService;
import com.easychat.utils.StringTools;
import org.springframework.web.multipart.MultipartFile;


/**
 * 消息表，一个会话下有多条消息 业务接口实现
 */
@Service("chatMessageService")
//@Slf4j
public class ChatMessageServiceImpl implements ChatMessageService {

	@Resource
	private ChatMessageMapper<ChatMessage, ChatMessageQuery> chatMessageMapper;
	@Resource
	private ChatSessionMapper<ChatSession, ChatSessionQuery> chatSessionMapper;
	@Resource
	private UserContactMapper<UserContact, UserContactQuery> userContactMapper;
	@Resource
	private RedisComponent redisComponent;
	@Resource
	private MessageHandler messageHandler;
	@Resource
	private AppConfig appConfig;
	@Resource
	private AsyncService asyncService;

	/**
	 * 根据条件查询列表
	 */
	@Override
	public List<ChatMessage> findListByParam(ChatMessageQuery param) {
		return this.chatMessageMapper.selectList(param);
	}

	/**
	 * 根据条件查询列表
	 */
	@Override
	public Integer findCountByParam(ChatMessageQuery param) {
		return this.chatMessageMapper.selectCount(param);
	}

	/**
	 * 分页查询方法
	 */
	@Override
	public PaginationResultVO<ChatMessage> findListByPage(ChatMessageQuery param) {
		int count = this.findCountByParam(param);
		int pageSize = param.getPageSize() == null ? PageSize.SIZE15.getSize() : param.getPageSize();

		SimplePage page = new SimplePage(param.getPageNo(), count, pageSize);
		param.setSimplePage(page);
		List<ChatMessage> list = this.findListByParam(param);
		PaginationResultVO<ChatMessage> result = new PaginationResultVO(count, page.getPageSize(), page.getPageNo(), page.getPageTotal(), list);
		return result;
	}

	/**
	 * 新增
	 */
	@Override
	public Integer add(ChatMessage bean) {
		return this.chatMessageMapper.insert(bean);
	}

	/**
	 * 批量新增
	 */
	@Override
	public Integer addBatch(List<ChatMessage> listBean) {
		if (listBean == null || listBean.isEmpty()) {
			return 0;
		}
		return this.chatMessageMapper.insertBatch(listBean);
	}

	/**
	 * 批量新增或者修改
	 */
	@Override
	public Integer addOrUpdateBatch(List<ChatMessage> listBean) {
		if (listBean == null || listBean.isEmpty()) {
			return 0;
		}
		return this.chatMessageMapper.insertOrUpdateBatch(listBean);
	}

	/**
	 * 多条件更新
	 */
	@Override
	public Integer updateByParam(ChatMessage bean, ChatMessageQuery param) {
		StringTools.checkParam(param);
		return this.chatMessageMapper.updateByParam(bean, param);
	}

	/**
	 * 多条件删除
	 */
	@Override
	public Integer deleteByParam(ChatMessageQuery param) {
		StringTools.checkParam(param);
		return this.chatMessageMapper.deleteByParam(param);
	}

	/**
	 * 根据MessageId获取对象
	 */
	@Override
	public ChatMessage getChatMessageByMessageId(Long messageId) {
		return this.chatMessageMapper.selectByMessageId(messageId);
	}

	/**
	 * 根据MessageId修改
	 */
	@Override
	public Integer updateChatMessageByMessageId(ChatMessage bean, Long messageId) {
		return this.chatMessageMapper.updateByMessageId(bean, messageId);
	}

	/**
	 * 根据MessageId删除
	 */
	@Override
	public Integer deleteChatMessageByMessageId(Long messageId) {
		return this.chatMessageMapper.deleteByMessageId(messageId);
	}

	/**
	 * 保存聊天消息
	 * @param chatMessage 聊天消息
	 * @param tokenUserInfoDTO 发送人token信息
	 * @return
	 */
	@Override
	public MessageSendDTO saveMessage(ChatMessage chatMessage, TokenUserInfoDTO tokenUserInfoDTO) {
		//该用户(发消息的人)不是机器人，判断chatMessage接收者是否为该用户(对应token)的好友
		if (!Constants.ROBOT_UID.equals(tokenUserInfoDTO.getUserId())) {
			List<String> contactList = redisComponent.getUserContact(tokenUserInfoDTO.getUserId());//获取该用户的联系人列表
			if (!contactList.contains(chatMessage.getContactId())) {//接收人不在该用户的联系人列表里
				UserTypeEnum userTypeEnum = UserTypeEnum.getByPrefix(chatMessage.getContactId());
				if (userTypeEnum == UserTypeEnum.USER) {//接收者不是该用户的好友
					throw new BusinessException(ResponseCodeEnum.CODE_902);
				} else if (userTypeEnum == UserTypeEnum.GROUP) {//接收者不是该用户所在群聊
					throw new BusinessException(ResponseCodeEnum.CODE_903);
				}
			}
		}

		//更新chat_message表
		String sessionId = null;
		String sendUserId = tokenUserInfoDTO.getUserId();//发送人id
		String contactId = chatMessage.getContactId();//联系人id
		//sendUserId sendUserNickName
		chatMessage.setSendUserId(sendUserId);
		chatMessage.setSendUserNickName(tokenUserInfoDTO.getNickName());
		//contactId
		chatMessage.setContactId(contactId);
		//sendTime
		Long curTime = System.currentTimeMillis();
		chatMessage.setSendTime(curTime);
		//sessionId
		UserTypeEnum contactTypeEnum = UserTypeEnum.getByPrefix(contactId);//联系人类型
		if (UserTypeEnum.USER == contactTypeEnum) {//根据联系人类型获取sessionId
			sessionId = StringTools.getChatSessionId4User(new String[]{contactId, sendUserId});
		} else {
			sessionId = StringTools.getChatSessionId4Group(contactId);
		}
		chatMessage.setSessionId(sessionId);
		//contactType
		chatMessage.setContactType(contactTypeEnum.getType());
		//messageType
		MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByType(chatMessage.getMessageType());//消息类型
		if (messageTypeEnum == null || !(MessageTypeEnum.CHAT == messageTypeEnum || MessageTypeEnum.MEDIA_CHAT == messageTypeEnum)) {
			//无法获取消息类型 / 消息类型不是聊天或者媒体文件
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		//status
		Integer status = null;//发送状态
		if (MessageTypeEnum.MEDIA_CHAT == messageTypeEnum) {
			status = MessageStatusEnum.LOADING.getStatus();//媒体文件初始状态：发送中
		} else {
			status = MessageStatusEnum.COMPLETED.getStatus();//普通聊天信息状态：已发送
		}
		chatMessage.setStatus(status);
		//messageContent
		String messageContent = StringTools.cleanHtmlTag(chatMessage.getMessageContent());//防止注入
		chatMessage.setMessageContent(messageContent);
		chatMessageMapper.insert(chatMessage);

		//更新会话chat_session
		ChatSession chatSession = new ChatSession();
		chatSession.setLastMessage(chatMessage.getMessageContent());
		if (UserTypeEnum.GROUP == contactTypeEnum) {
			//联系人为群组，最后的消息显示为：发送人昵称：说的话
			chatSession.setLastMessage(tokenUserInfoDTO.getNickName() + "：" + chatMessage.getMessageContent());
		}
		chatSession.setLastReceiveTime(curTime);
		chatSessionMapper.updateBySessionId(chatSession, sessionId);//聊天时，会话已经存在，所以是更新而不是插入

		//发消息
		MessageSendDTO messageSendDTO = new MessageSendDTO();
		BeanUtils.copyProperties(chatMessage, messageSendDTO);
		//特殊处理：发给机器人的消息
		SysSettingDTO sysSetting = redisComponent.getSysSetting();
		String robotUid = sysSetting.getRobotUid();
		if (Constants.ROBOT_UID.equals(contactId)) {
			//如果是发给机器人的消息，则立即回复一条消息给发送者
			TokenUserInfoDTO robotToken = new TokenUserInfoDTO();
			robotToken.setUserId(robotUid);
			robotToken.setNickName(sysSetting.getRobotNickName());
			ChatMessage robotChatMessage = new ChatMessage();
			robotChatMessage.setContactId(sendUserId);
			//TODO 这里可以接入AI，就不是每次回复固定消息了
			//开启异步任务
			asyncService.executeAsync(messageContent, sendUserId);
			robotChatMessage.setMessageContent("我正在思考，请耐心等待...");
			robotChatMessage.setMessageType(MessageTypeEnum.CHAT.getType());
			saveMessage(robotChatMessage, robotToken);//机器人发消息给人类
		} else {
			messageHandler.sendMessage(messageSendDTO);
		}

		return messageSendDTO;
	}

	/**
	 * 保存聊天文件
	 * @param userId 发送人ID
	 * @param messageId 消息ID
	 * @param file 文件
	 * @param cover 封面
	 */
	@Override
	public void saveMessageFile(String userId, Long messageId, MultipartFile file, MultipartFile cover) {
		ChatMessage chatMessage = chatMessageMapper.selectByMessageId(messageId);
		if (chatMessage == null) {
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		if (!chatMessage.getSendUserId().equals(userId)) {
			//messageId对应的消息发送者不是userId
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		SysSettingDTO sysSetting = redisComponent.getSysSetting();
		String fileExtension = StringTools.getFileExtension(file.getOriginalFilename());//获取文件后缀
		//文件分为三种：图片，视频，其他
		if (StringTools.isEmpty(fileExtension)) {
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		if (
			ArrayUtils.contains(Constants.IMAGE_EXTENSION_LIST, fileExtension.toLowerCase())
			&& file.getSize() > sysSetting.getMaxImageSize() * Constants.FILE_SIZE_MB
		) {
			//图片的情况 注意getSize()结果单位为B字节
			throw new BusinessException(ResponseCodeEnum.CODE_600);//文件大小超过限制
		} else if (
			ArrayUtils.contains(Constants.VIDEO_EXTENSION_LIST, fileExtension.toLowerCase())
			&& file.getSize() > sysSetting.getMaxVideoSize() * Constants.FILE_SIZE_MB
		) {
			//视频的情况
			throw new BusinessException(ResponseCodeEnum.CODE_600);//文件大小超过限制
		} else if (
				!ArrayUtils.contains(Constants.IMAGE_EXTENSION_LIST, fileExtension.toLowerCase())
				&& !ArrayUtils.contains(Constants.VIDEO_EXTENSION_LIST, fileExtension.toLowerCase())
				&& file.getSize() > sysSetting.getMaxFileSize() * Constants.FILE_SIZE_MB
		) {
			//其他的情况
			throw new BusinessException(ResponseCodeEnum.CODE_600);//文件大小超过限制
		}
		//利用messageId为文件命名，防止覆盖
		String fileRealName = messageId + fileExtension;
		//按发送时间对文件进行分目录 得到发送时间对应的日期，再将其格式化
		String month = DateUtil.format(new Date(chatMessage.getSendTime()), DateTimePatternEnum.YYYYMM.getPattern());
		File storageFolder = new File(appConfig.getProjectFolder() + month);
		//如果不存在此目录，要创建
		if (!storageFolder.exists()) {
			storageFolder.mkdirs();
		}
		//要存储的文件和封面
		File uploadFile = new File(storageFolder.getPath() + "/" + fileRealName);
		try {
			file.transferTo(uploadFile);
			cover.transferTo(new File(uploadFile.getPath() + Constants.COVER_IMAGE_EXTENSION));//封面命名方法
		} catch (IOException e) {
			//log.error("文件上传失败");
			throw new BusinessException("文件上传失败");
		}
		//更新chat_message表的发送状态
		ChatMessage updateInfo = new ChatMessage();
		updateInfo.setStatus(MessageStatusEnum.COMPLETED.getStatus());//发送完成
		chatMessageMapper.updateByMessageId(updateInfo, messageId);
		//发消息给接收方，提示我方文件已上传服务器，可以开始下载了
		MessageSendDTO messageSendDTO = new MessageSendDTO();
		messageSendDTO.setStatus(MessageStatusEnum.COMPLETED.getStatus());
		messageSendDTO.setMessageType(MessageTypeEnum.FILE_UPLOAD.getType());
		messageSendDTO.setMessageId(messageId);
		messageSendDTO.setContactId(chatMessage.getContactId());//注意消息要发给接收文件的人
		messageHandler.sendMessage(messageSendDTO);
	}

	/**
	 * 获取fileId对应的File对象(文件在本地)
	 * @param tokenUserInfoDTO 对应要下载文件的客户端（消息接收方）
	 * @param fileId 文件ID（非头像文件，fileId为messageId，纯数字）
	 * @param showCover 是否展示封面
	 * @return File对象
	 */
	@Override
	public File downloadFile(TokenUserInfoDTO tokenUserInfoDTO, Long fileId, Boolean showCover) {
		ChatMessage chatMessage = chatMessageMapper.selectByMessageId(fileId);
		String contactId = chatMessage.getContactId();
		UserTypeEnum contactTypeEnum = UserTypeEnum.getByPrefix(contactId);
		if (UserTypeEnum.USER == contactTypeEnum && !tokenUserInfoDTO.getUserId().equals(contactId)) {
			//单聊，判断fileId对应的聊天消息的接收人是否正确
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		if (UserTypeEnum.GROUP == contactTypeEnum) {
			//群聊，判断token对应的用户是否在fileId对应的聊天消息所属的群聊内
			UserContactQuery userContactQuery = new UserContactQuery();
			userContactQuery.setContactId(contactId);//群id
			userContactQuery.setUserId(tokenUserInfoDTO.getUserId());
			userContactQuery.setContactType(UserTypeEnum.GROUP.getType());
			userContactQuery.setStatus(UserContactStatusEnum.FRIEND.getStatus());
			Integer contactCount = userContactMapper.selectCount(userContactQuery);
			if (contactCount == 0) {
				throw new BusinessException(ResponseCodeEnum.CODE_600);
			}
		}
		//根据消息发送时间，获取文件夹名称
		String month = DateUtil.format(new Date(chatMessage.getSendTime()), DateTimePatternEnum.YYYYMM.getPattern());
		String storageFolder = appConfig.getProjectFolder() + month;//文件夹路径
		String fileExtension = StringTools.getFileExtension(chatMessage.getFileName());//获取文件后缀
		String fileRealName = fileId + fileExtension;//文件真名：消息id（文件id）+ 后缀
		if (showCover != null && showCover) {
			fileRealName = fileRealName + Constants.COVER_IMAGE_EXTENSION;//展示封面
		}
		File file = new File(storageFolder + "/" + fileRealName);
		if (!file.exists()) {
			//要下载的文件不存在
			//log.error("文件不存在，messageId = {}", fileId);
			throw new BusinessException(ResponseCodeEnum.CODE_602);
		}
		return file;
	}
}